<?php
require_once 'Zend/Amf/Server.php';

/**
 * Extension to Zend_Amf library, adds configurable data services that
 * allow you quering, inserting and updating Zend_Db table directly from Flex apps.
 * 
 * @author Piotr Walczyszyn (www.riaspace.net)
 *
 */
class ZendAmfDs extends Zend_Amf_Server
{
	/**
	 * XML element with ZendAmfDs service configuration
	 * @var SimpleXMLElement
	 */
	public static $config = null;

	/**
	 * ZendAmfDs constructor, requires one parameter with path to 
	 * queries configuration file.
	 * 
	 * @param string $configPath
	 * @return unknown_type
	 */
	public function __construct($configPath)
	{
		
		$xml = simplexml_load_file($configPath);
		
		// Getting insert queries to map dummy object on PHP side
		$asClasses = $xml->xpath("/service/destination[@id='ZendAmfDs']/properties/insert/asClass");
		if ($asClasses != false)
		{
			foreach($asClasses as $asClass)
			{
				parent::setClassMap(trim($asClass), 'RemoteClass');	
			}
		}

		$destination = $xml->xpath("/service/destination[@id='ZendAmfDs']/properties");
		self::$config = $destination[0];
	
		parent::addFunction('query');
	}
}

// This is dummy class to workaround issue with not mapped PHP classes
class RemoteClass {}

/**
 * Service main function
 * 
 * @param string $queryName 
 * @param array $queryParams
 * @return UNKNOWN_TYPE
 */
function query($queryName, $queryParams = null)
{
	// Getting query config based on passed method parameter
	$queryConfig = ZendAmfDs::$config->xpath("*[@id='" . $queryName . "']");
	$queryConfig = $queryConfig[0];

	// Getting query type
	$queryType = $queryConfig->getName();

	// Importing model class
	$modelTableClass = (string) $queryConfig->modelClass;
	require_once APPLICATION_PATH . '/' . (string) ZendAmfDs::$config->defaults->modelsPath . '/' . $modelTableClass . '.php';

	// Creating table class
	$table = new $modelTableClass();

	switch ($queryType) {
			
		case "select":
			return executeSelectQuery($table, $queryParams, $queryConfig);
			break;

		case "insert":
			return executeInsertQuery($table, $queryParams, $queryConfig);
			break;
			
		case "update":
			return executeUpdateQuery($table, $queryParams, $queryConfig);
			break;
			
		default:
			throw new Exception("Unsupported query type: " . $queryType);
			break;
				
	}
	return null;
}

function executeSelectQuery($table, $queryParams, $queryConfig)
{
	// Creating mapping between table column names and AS properties
	$columnsMapping = getColumnsMapping($table->info('cols'));

	// Creating select statement
	$select = $table->select();
	// Setting where clause
	if (isset($queryConfig->where))
	{
		$select->where((string) $queryConfig->where, $queryParams);
	}
	// Setting order clause
	if (isset($queryConfig->order))
	{
		$select->order((string) $queryConfig->order);
	}
	// Fetching selects statement all rows
	$tableRowset = $table->fetchAll($select);

	$metadata = $table->info('metadata');
	
	$result = array();
	foreach($tableRowset as $row)
	{
		$resultObj = new stdClass();
		$resultObj->_explicitType = (string) $queryConfig->asClass;

		$rowArray = $row->toArray();
		foreach($rowArray as $column => $value)
		{
			$propertyName = $columnsMapping[$column];
			
			$datatype = $metadata[$column]['DATA_TYPE'];
			if ($datatype == 'datetime' || $datatype == 'timestamp')
			{
				$resultObj->$propertyName = new DateTime($value);
			}
			else
			{
				$resultObj->$propertyName = $value;
			}
		}
		array_push($result, $resultObj);
	}

	return $result;
}

function executeInsertQuery($table, $queryParams, $queryConfig)
{
	if ($queryParams == null || count($queryParams) != 1)
	{
		throw new Exception("Insert query requires one parameter with object to be inserted!");
	}
	
	// Getting primary keys array
	$pks = $table->info('primary');
	
	$properties = (array) $queryParams[0];
	$entity = new stdClass();
	foreach($properties as $name => $value)
	{
		// TODO: this should take info from configuration file about
		$columnName = strtoupper(preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '_$0', preg_replace('/(?!^)[[:upper:]]+/', '$0', $name)));
		
		// TODO: this is a workaround for null primary key, it nulls value if it is a primary key value
		if (in_array($columnName, $pks) && is_nan($value))
		{
			$value = null;
		}
		
		$entity->$columnName = $value;
	}
	return $table->insert((array) $entity);
}

function executeUpdateQuery($table, $queryParams, $queryConfig)
{
	throw new Exception("Update query not implemented yet!!!");
	return null;
}

 function getColumnsMapping($colsInfo)
{
	$result = array();
	foreach($colsInfo as $columnName)
	{
		$propertyName = camelizeColumn($columnName);
		$result[$columnName] = $propertyName;
	}
	return $result;
}

 function camelizeColumn($columnName)
{
	if (!isset(ZendAmfDs::$config->defaults->columnNameCamelSeparator))
	{
		return $columnName;
	}
	else
	{
		$result = "";
		$splitted = split(ZendAmfDs::$config->defaults->columnNameCamelSeparator, strtolower($columnName));
		for ($i = 0; $i < count($splitted); $i++)
		{
			if ($i == 0)
			{
				$result .= $splitted[$i];
			}
			else
			{
				$result .= ucfirst($splitted[$i]);
			}
		}
	}
	return $result;
}	
